/*
 * $AIST_Release: 0.9.0 $
 * Copyright 2011 Information Technology Research Institute, National
 * Institute of Advanced Industrial Science and Technology
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *    http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package parser.analyzer

import scala.collection.JavaConversions._
import scala.collection.mutable.{Map => MMap}
import scala.collection.mutable.ListBuffer
import scala.util.parsing.input.Position

import com.google.protobuf.DescriptorProtos._
import com.google.protobuf.Descriptors._

import util.PBUtils

import parser.Nodes.Label
import parser.DataTypes._
import parser.Nodes._
import parser.Results._
import parser.TreeWalker

/**
 * Genarates ProtocolBuffers Message from table type.
 */
object TypeAnalyzer {

  /**
   * Result of type analysis.
   *
   * 
   */
  case class State(ast: List[Stmt], typeMap: Map[Int, DataType])

  def analyze(ast: List[Stmt], fds: List[FileDescriptor], pos: Map[String, Position]): Result[State] = {
    makeTypeTableFromFDs(fds, pos).flatMap { analyzeTypes(ast, _) }
  }

  /**
   * Make type information from FileDescriptors.
   */
  private def makeTypeTableFromFDs(fds: List[FileDescriptor], pos: Map[String, Position]) = {
    val typeTable = MMap[String, DataType]()

    mergeList(fds.flatMap { fd =>
      val types = PBUtils.getMessages(fd).map(mkTuple) ++ 
                  PBUtils.getEnums(fd).map(mkEnum)
      types.map { case (name, dataType) =>
        if (typeTable.contains(name)) {
          failure(pos(fd.getName), "Data type \"%s\" has already been defined.".format(name))
        } else {
          typeTable(name) = dataType
          Success(Unit)
        }
      }
    }).mapConst(typeTable)
  }

  /**
   * Makes enum type from EnumDescriptor
   */
  private def mkEnum(e: EnumDescriptor): (String, DataType) = {
    (e.getName, EnumType(e))
  }

  /**
   * Makes tuple type from EnumDescriptor
   */
  private def mkTuple(d: Descriptor): (String, DataType) = {
    def fieldType(fldd: FieldDescriptor) = {
      import FieldDescriptor.JavaType._
      val basic = fldd.getJavaType() match {
        case ENUM        => ImperfectType(fldd.getEnumType.getName)
        case MESSAGE     => ImperfectType(fldd.getMessageType.getName)
        case BOOLEAN     => IntType
        case BYTE_STRING => BytesType
        case DOUBLE      => FloatType
        case FLOAT       => FloatType
        case INT         => IntType
        case LONG        => IntType
        case STRING      => StringType
      }
      if (fldd.isRepeated) ArrayType(basic)
      else                 basic
    }
    val members = d.getFields.map(fldd => (fldd.getName, fieldType(fldd))) 

    (d.getName, MessageType(d, members.toList))
  }

  /**
   * Analyzes data types in AST.
   */
  private def analyzeTypes(ast: List[Stmt], typeTable: MMap[String, DataType]) = {
    val idGen = Stream.from(0).iterator
    val types: ListBuffer[MMap[String, Int]] = ListBuffer(MMap())
    val typeMap: MMap[Int, DataType] = MMap()

    def analyzeLabel(label: Label) = {
      analyzeDataType(label.dataType).map { 
        Label(label.name, _).setPos(label.pos) 
      }
    }

    def analyzeDataType(dataType: DataType): Result[DataType] = {
      (TreeWalker.mapTree(classOf[DataType], dataType) {
        case t: ImperfectType => resolveImperfectType(t)
        case x: FunctionList  => error("Invalid type")
        case x: RefType       => error("Invalid type")
        case x: TableType     => error("Invalid type")
        case x: WildCardType  => error("Invalid type")
        case UnkownType       => error("Invalid type")
      }).asInstanceOf[Result[DataType]]
    }

    def resolveImperfectType(t: ImperfectType): Result[DataType] = {
      buildInTypes.get(t.name) match {
        case Some(b) => Success(b)
        case None =>
          val it = types.reverseIterator
          while (it.hasNext) {
            val m = it.next
            if (m.contains(t.name)) {
              return Success(TypeReference(m(t.name)))
            }
          }
          return failure(t.pos, "Unkown type %s", t.name)
      }
    }

    // Make TypeMap from typeTable
    typeTable.foreach { case (name, dataType) =>
      assert(!types.last.contains(name))
      types.last(name) = idGen.next
    }

    types.last.foreach { case (name, id) =>
      analyzeDataType(typeTable(name)) match { 
        case Success(dataType, _) => 
          typeMap(id) = dataType
        case x: Failure => error(x.msgs.mkString("\n"))
      }
    }

    val analyzedAST = TreeWalker.Mapper(classOf[Node]).setPostprocess {
      case s: DefType =>
        if (types.last.contains(s.name)) {
          failure(s.pos, "Type %s already has defined.".format(s.name))
        } else {
          analyzeDataType(s.impl).map { dt =>
            val i = idGen.next
            types.last(s.name) = i
            typeMap(i) = dt
            s
          }
        }

      case s: DefVarStmt =>
        analyzeDataType(s.dataType).map { t =>
          s.copy(dataType = t).setPos(s.pos)
        }

      case s: DefTableStmt =>
        val k = mergeList(s.info.indexes.map(analyzeLabel))
        val c = analyzeLabel(s.info.conType)
        val o = mergeList(s.info.opts.map { case TableOption(id, label) => 
          analyzeLabel(label).map { label =>
            TableOption(id, label)
          }
        })

        (k ~ c ~ o).map { case k ~ c ~ o =>
          s.copy(info = s.info.copy(indexes = k, conType = c, opts = o)).setPos(s.pos)
        }

      case s@WhenStmt(vars, cond, stmt) =>
        def analyzeWhenDefVar(defVar: WhenDefVar) = {
          analyzeDataType(defVar.dataType).map { dataType => defVar.copy(dataType = dataType) }
        }

        mergeList(vars.map(analyzeWhenDefVar)).map { case defvars =>
          WhenStmt(defvars, cond, stmt).setPos(s.pos)
        }
      case expr@FuncLit(ret, args, stmts) =>
        (analyzeDataType(ret) ~ mergeList(args.map(analyzeLabel))).map { case ret ~ args =>
          FuncLit(ret, args, stmts).setPos(expr.pos)
        }

    }.setFollower {
      f => {
        case s: Scope =>
          types.append(MMap())
          try     f(s.stmt).map{ case s: Stmt => Scope(s) }
          finally types.trimEnd(1)
      }
    }.map(ast)

    analyzedAST.map { case ast: List[Stmt] => State(ast, Map() ++ typeMap) }
  }
}
